package jdk8.stream;

import jdk8.vo.Transaction;
import lombok.Data;

import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.*;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;

import static java.util.Comparator.comparing;
import static java.util.stream.Collectors.toList;

/**
 * @Desc:   在 Java 8 使用 Stream，代码更加简洁易读；而且使用并发模式，程序执行速度更快
 * @author: cww
 * @DateTime: 2020/6/3 17:19
 */

public class ListStream {
    static List<Transaction> transactions = new ArrayList<>();
    public static void main(String[] args) {
        constructStream();


    }

    /**
     * java 7 时候需要实现 排序、取值 的实现
     */
    public void beforIterator() {
        List<Transaction> transactions = new ArrayList<>();
        List<Transaction> groceryTransactions = new ArrayList<>();
        // 筛选类型
        for (Transaction t : transactions) {
            if (t.getType().equals("xxx")) {
                groceryTransactions.add(t);
            }
        }
        // 排序
        Collections.sort(groceryTransactions, new Comparator<Transaction>(){
            public int compare(Transaction t1, Transaction t2){
                return t2.getValue().compareTo(t1.getValue());
            }
        });
        // 取值
        List<Integer> transactionIds = new ArrayList<>();
        for (Transaction t : groceryTransactions) {
            transactionIds.add(t.getId());
        }

        groceryTransactions.stream().filter(t-> t.getType().equals("xxx")).sorted(comparing(Transaction::getId)).forEach(t -> transactionIds.add(t.getValue()));

    }

    /**
     * java8 使用 stream
     */
    public static void jdk8Iterator() {
        Transaction transaction= new Transaction();
        transaction.setId(1);
        transaction.setValue(10);
        transaction.setType("A");
        Transaction transaction1= new Transaction();
        transaction.setId(2);
        transaction.setValue(11);
        transaction.setType("B");
        Transaction transaction2= new Transaction();
        transaction.setId(3);
        transaction.setValue(12);
        transaction.setType("A");
        Transaction transaction3= new Transaction();
        transaction.setId(4);
        transaction.setValue(13);
        transaction.setType("A");
        transactions.add(transaction);
        transactions.add(transaction1);
        transactions.add(transaction2);
        transactions.add(transaction3);
        List<Integer> transactionIds = transactions.parallelStream().filter(t -> t.getType().equals("A"))
                .map(Transaction::getId).collect(toList());
        System.out.println();

    }

    /**
     * 构造流的几种常见方法
     */
    public static void constructStream() {
        Stream stream = Stream.of("a","c","b");
        String[] strArray = new String[]{"a","c","b","ar"};
        Stream stream1 = Stream.of(strArray);
        Stream stream2 = Arrays.stream(strArray);
        List<String> arrayList = Arrays.asList(strArray);
        Stream stream3 = Stream.of(arrayList);
        List<String> list = new ArrayList<>();
        arrayList.stream().sorted().filter(t -> t.length()>=1).forEach(r -> list.add(r));
        System.out.println(list.toString());
        int s = Arrays.stream(strArray).mapToInt(c -> Integer.valueOf(c.charAt(0))).sum();
        System.out.println(s);
        Arrays.stream(strArray).filter(t -> t.contains("a")).forEach(System.out::println);
        Arrays.stream(strArray).map(t -> t.concat(".txt")).forEach(System.out::println);
        Stream<String> s33 = Stream.of("test", "t1", "t2", "teeeee", "aaaa");
        s33.map(t -> t.split("")).forEach(t -> System.out.println(t.toString()));

    }

    /**
     * 数值流的构造
     */
    public static void NumberStream() {
        IntStream.of(new int[]{1,2,3}).forEach(System.out::println);
        IntStream.range(1,3).forEach(System.out::println);
        IntStream.rangeClosed(1,3).forEach(System.out::println);
        int[] nu = new int[]{100, 4, 200, 1, 3, 2};

        Stream s = Stream.of(nu);
        Set set1 = new HashSet<>();
        s.forEach(i -> set1.add(i));


        System.out.println();
    }

    /**
     * 流转换为其它数据结构
     * 一个 Stream 只可以使用一次，代码为了简洁而重复使用了数次。
     */
    public void StreamToOthers() {
        // array
        Stream stream = Stream.of("a","b","c");
        // collection
        String[] strArray1 = (String[]) stream.toArray(String[]::new);
        List<String> list1 = (List<String>) stream.collect(Collectors.toList());
        List<String> list2 = (List<String>) stream.collect(Collectors.toCollection(ArrayList::new));
        Set set1 = (Set) stream.collect(Collectors.toSet());
        Stack stack1 = (Stack) stream.collect(Collectors.toCollection(Stack::new));
        // string
        String str = stream.collect(Collectors.joining()).toString();
    }

    /**
     * map/flatMap 转换，把 input Stream 的每一个元素，映射成 output Stream 的另外一个元素
     */
    public static void mapStream() {
        List<String> wordList = new ArrayList<>();
        // ------------map 生成的是个 1:1 映射---------------

        // 把所有的单词转换为大写
        List<String> output = wordList.stream().map(String::toUpperCase).collect(Collectors.toList());

        /**
         * 平方数
         */
        List<Integer> nums = Arrays.asList(1,2,3,4);
        List<Integer> squareNums = nums.stream().map(n -> n*n).collect(Collectors.toList());
        // -----------------------------------------------

        //------------------一对多映射关系 flatMap---------
        // 一对多 flatMap 把 input Stream 中的层级结构扁平化，就是将最底层元素抽出来放到一起，
        // 最终 output 的新 Stream 里面已经没有 List 了，都是直接的数字
        Stream<List<Integer>> inputStream = Stream.of(
                Arrays.asList(1),Arrays.asList(2,3),Arrays.asList(4,5,6)
        );
        Stream<Integer> outputStream = inputStream.flatMap((childList) -> childList.stream());
        System.out.println("");
    }

    /**
     * filter 对原始 Stream 进行某项测试，通过测试的元素被留下来生成一个新 Stream
     */
    public static void filterStream() {
        // 留下偶数
        Integer[] sixNums = {1,2,3,4,5,6};
        Integer[] evens = Stream.of(sixNums).filter(n -> n%2 == 0).toArray(Integer[]::new);
        System.out.println();

        // 把单词挑出来
        // 首先把每行的单词用 flatMap 整理到新的 Stream，然后保留长度不为 0 的，就是整篇文章中的全部单词了
        BufferedReader reader = null;
        try {
            reader = new BufferedReader(new FileReader(""));
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        List<String> output = (List<String>) reader.lines().flatMap(line -> Stream.of(line.split(" ")))
                .filter(word -> word.length() >0).collect(toList());
    }

    /**
     * forEach
     *
     * 需要注意，forEach 是 terminal 操作，因此它执行后，Stream 的元素就被“消费”掉了，
     * 你无法对一个 Stream 进行两次 terminal 运算。
     */
    public static void forEachStream() {
        List<Transaction> transactions = new ArrayList<>();
        // forEach 方法接收一个 Lambda 表达式，然后在 Stream 的每一个元素上执行该表达式
        // forEach 不能修改自己包含的本地变量值，也不能用 break/return 之类的关键字提前结束循环
        transactions.stream().filter(p -> p.getType() == "")
                .forEach(p -> System.out.println(p.getValue()));

        // peek 对每个元素执行操作并返回一个新的 Stream, 但是foreach 不能这样重复打印
        Stream.of("one", "two", "three", "four")
                .filter(e -> e.length() > 3)
                .peek(e -> System.out.println("Filetered value:" + e))
                .map(String::toUpperCase)
                .peek(e -> System.out.println("Mapped value:" +e))
                .collect(Collectors.toList());
    }

    /**
     * findFirst
     * 这是一个 terminal 兼 short-circuiting 操作，它总是返回 Stream 的第一个元素，或者空
     * 这里比较重点的是它的返回值类型：Optional。这也是一个模仿 Scala 语言中的概念，作为一个容器，
     * 它可能含有某值，或者不包含。使用它的目的是尽可能避免 NullPointerException
     * 在更复杂的 if (xx != null) 的情况中，使用 Optional 代码的可读性更好，而且它提供的是编译
     * 时检查，能极大的降低 NPE 这种 Runtime Exception 对程序的影响，或者迫使程序员更早的在编
     * 码阶段处理空值问题，而不是留到运行时再发现和调试。
     *
     * Stream 中的 findAny、max/min、reduce 等方法等返回 Optional 值。还有例如 IntStream.average()
     * 返回 OptionalDouble 等等
     */
    public static void findFirstStream() {
        String text = null;
        // Java 8
        Optional.ofNullable(text).ifPresent(System.out::println);
        // Pre-Java 8
        if (text != null) {
            System.out.println(text);
        }
        // Java 8
        int length = Optional.ofNullable(text).map(String::length).orElse(-1);
        // Pre-Java 8
        int length1 = text != null ? text.length() : -1;
    }

    /**
     * reduce
     * 这个方法的主要作用是把 Stream 元素组合起来
     * 它提供一个起始值（种子），然后依照运算规则（BinaryOperator），
     * 和前面 Stream 的第一个、第二个、第 n 个元素组合
     * 从这个意义上说，字符串拼接、数值的 sum、min、max、average 都是特殊的 reduce
     * 也有没有起始值的情况，这时会把 Stream 的前面两个元素组合起来，返回的是 Optional
     */
    public static void reduceStream() {
        // 字符串连接，concat = "ABCD"  -- 第一个参数（空白字符）即为起始值，第二个参数（String::concat）为 BinaryOperator
        String concat = Stream.of("A","B","C","D").reduce("", String::concat);

        // 求最小值
        double minValue = Stream.of(-1.5,1.0,-3.0,-2.0).reduce(Double.MAX_VALUE, Double::min);
        // 求和 sumValue = 10, 有起始值
        int sumVlaue = Stream.of(1,2,3,4).reduce(0, Integer::sum);
        // 求和 sumValue = 10
        // 对于这个示例没有起始值的 reduce()，由于可能没有足够的元素，返回的是 Optional，请留意这个区别
        Integer[] i= new Integer[4];// 这个 i 放入下面会抛出空指针异常
        sumVlaue = Stream.of(1,2,3).reduce(Integer::sum).get();
        System.out.println(sumVlaue);
        // 过滤，字符串连接，concat = "ace"
        concat = Stream.of("a","b","c","D","e","F").reduce("",String::concat);

    }

    /**
     * limit/skip
     * limit 返回 Stream 的前面 n 个元素；skip 则是扔掉前 n 个元素（它是由一个叫 subStream 的方法改名而来）
     *
     * 注意：有一种情况是 limit/skip 无法达到 short-circuiting 目的的，就是把它们放在 Stream 的排序操作后，
     * 原因跟 sorted 这个 intermediate 操作有关：此时系统并不知道 Stream 排序后的次序如何，所以 sorted 中
     * 的操作看上去就像完全没有被 limit 或者 skip 一样
     *
     * 最后有一点需要注意的是，对一个 parallel 的 Steam 管道来说，如果其元素是有序的，那么 limit 操作的成
     * 本会比较大，因为它的返回对象必须是前 n 个也有一样次序的元素。取而代之的策略是取消元素间的次序，或者
     * 不要用 parallel Stream
     *
     *
     *
     */
    public static void limitSkipStream() {
        List<Person> persons = new ArrayList();
        for (int i = 1; i <= 10000; i++) {
            Person person = new Person(i, "name" + i, 10);
            persons.add(person);
        }
        // 管道中 map 操作指定的 getName() 方法的执行次数为 limit 所限定的 10 次，
        // 而最终返回结果在跳过前 3 个元素后只有后面 7 个返回
        List<String> personList2 = persons.stream().map(Person::getName).limit(10).skip(3).collect(toList());
        System.out.println(personList2);

        // limit 和 skip 对 sorted 后的运行次数无影响
        List<Person> persons1 = new ArrayList();
        for (int i = 1; i <= 5; i++) {
            Person person = new Person(i, "name" + i, 20);
            persons1.add(person);
        }

        // 首先对 5 个元素的 Stream 排序，然后进行 limit 操作,见结果，最后返回的元素数量是2，，
        // 但整个管道中的 sorted 表达式执行次数没有像前面例子相应减少
        List<Person> personList = persons1.stream().sorted((p1,p2) -> p1.getName().compareTo(p2.getName()))
                .limit(2).collect(toList());
        System.out.println(personList);

    }

    @Data
    private static class Person {
        public int no;
        private String name;
        private Integer age;

        public Person(int no, String name, Integer age) {
            this.no = no;
            this.name = name;
        }

        public String getName() {
            System.out.println(name);
            return name;
        }
    }

    /**
     * sorted
     * 对 Stream 的排序通过 sorted 进行，它比数组的排序更强之处在于你可以首先对 Stream
     * 进行各类 map、filter、limit、skip 甚至 distinct 来减少元素数量后，再排序，这能
     * 帮助程序明显缩短执行时间
     *
     * 当然，这种优化是有 business logic 上的局限性的：即不要求排序后再取值
     */
    public static void sortedStream() {
        List<Person> persons = new ArrayList<>();
        for (int i = 1; i < 5; i++) {
            Person person = new Person(i, "name" + i, 10);
            persons.add(person);
        }

        List<Person> personList = persons.stream().limit(2).sorted((p1,p2) -> p1.getName().compareTo(p2.getName())).collect(toList());
        System.out.println(personList);
    }

    /**
     * min/max/distinct
     * min 和 max 的功能也可以通过对 Stream 元素先排序,再 findFirst 来实现
     * 但前者的性能会更好，为 O(n)，而 sorted 的成本是 O(n log n)
     * 同时它们作为特殊的 reduce 方法被独立出来也是因为求最大最小值是很常见的操作
     *
     * .max() 方法返回的是 Optional
     */
    public static void mathStream() throws IOException {
        BufferedReader reader = new BufferedReader(new FileReader(""));
        // 找出最长一行的长度
        int longest = reader.lines().mapToInt(String::length).max().getAsInt();
        reader.close();
        System.out.println(longest);
        // 找出全文的单词，转小写，并排序
        List<String> words = reader.lines().flatMap(line -> Stream.of(line.split(" ")))
                .filter(word -> word.length()>0).map(String::toUpperCase).sorted().collect(toList());
        System.out.println(words);
    }

    /**
     * Match
     * Stream 有三个 match 方法，从语义上说：
     *
     *      allMatch：Stream 中全部元素符合传入的 predicate，返回 true
     *      anyMatch：Stream 中只要有一个元素符合传入的 predicate，返回 true
     *      noneMatch：Stream 中没有一个元素符合传入的 predicate，返回 true
     *
     * 它们都不是要遍历全部元素才能返回结果。例如 allMatch 只要一个元素不满足条件，
     * 就 skip 剩下的所有元素，返回 false
     */
    public static void matchStream() {
        List<Person> persons = new ArrayList();
        persons.add(new Person(1, "name" + 1, 10));
        persons.add(new Person(2, "name" + 2, 21));
        persons.add(new Person(3, "name" + 3, 34));
        persons.add(new Person(4, "name" + 4, 6));
        persons.add(new Person(5, "name" + 5, 55));
        boolean isAllAdult = persons.stream().allMatch(p -> p.getAge() > 18);
        System.out.println("All are adult? " + isAllAdult);
        boolean isThereAnyChild = persons.stream().anyMatch(p -> p.getAge()<12);
        System.out.println("Any child? " + isThereAnyChild);
    }

    // -------------- 进阶：自己生成流 ---------------

    /**
     * Stream.generate
     *
     * 通过实现 Supplier 接口，你可以自己来控制流的生成。这种情形通常用于随机数、
     * 常量的 Stream，或者需要前后元素间维持着某种状态信息的 Stream。把 Supplier
     * 实例传递给 Stream.generate() 生成的 Stream，默认是串行（相对 parallel 而言）
     * 但无序的（相对 ordered 而言）。由于它是无限的，在管道中，必须利用 limit 之类
     * 的操作限制 Stream 大小。
     */

    /**
     * 生成 10 个随机整数
     */
    public static void randomStream() {
        Random seed = new Random();
        Supplier<Integer> random = seed::nextInt;
        Stream.generate(random).limit(10).forEach(System.out::println);
        // Another way
        IntStream.generate(() -> (int) (System.nanoTime() % 100)).limit(10).forEach(System.out::println);
    }

    /**
     * Stream.generate() 还接受自己实现的 Supplier。例如在构造海量测试数据的时候，
     * 用某种自动的规则给每一个变量赋值；或者依据公式计算 Stream 的每个元素值。
     * 这些都是维持状态信息的情形
     */
    public static void supplierStream() {
        Stream.generate(new PersonSupplier()).limit(10)
                .forEach(p -> System.out.println(p.getName() + "," + p.getAge()));

    }

    static class PersonSupplier implements  Supplier<Person> {
        private int index = 0;
        private Random random = new Random();

        /**
         * Gets a result.
         *
         * @return a result
         */
        @Override
        public Person get() {
            return new Person(index++, "StormTestUser" + index, random.nextInt(100));
        }
    }

    /**
     * iterate 跟 reduce 操作很像，接受一个种子值，和一个 UnaryOperator（例如 f）。
     * 然后种子值成为 Stream 的第一个元素，f(seed) 为第二个，f(f(seed)) 第三个，以此类推
     * 生成一个等差数列
     *
     * 与 Stream.generate 相仿，在 iterate 时候管道必须有 limit 这样的操作来限制 Stream 大小
     */
    public static void iterateStream() {
        Stream.iterate(0,n -> n+3).limit(10).forEach(x -> System.out.println(x + " "));
    }


    // 进阶：用 Collectors 来进行 reduction 操作
    /**
     * java.util.stream.Collectors 类的主要作用就是辅助进行各类有用的 reduction 操作，
     * 例如转变输出为 Collection，把 Stream 元素进行归组
     */

    /**
     * groupingBy/partitioningBy
     *
     */
    public static void groupingByStream() {
        // 按照年龄归组
        Map<Integer, List<Person>> personGroups = Stream.generate(new PersonSupplier()).
                limit(100).
                collect(Collectors.groupingBy(Person::getAge));
        Iterator it = personGroups.entrySet().iterator();
        while (it.hasNext()) {
            Map.Entry<Integer, List<Person>> persons = (Map.Entry<Integer, List<Person>>) it.next();
            System.out.println("Age " + persons.getKey() + " = " + persons.getValue().size());
        }

        // 按照未成年人和成年人归组
        // partitioningBy 其实是一种特殊的 groupingBy，它依照条件测试的是否两种结果来构造返回的数据
        // 结构，get(true) 和 get(false) 能即为全部的元素对象
        Map<Boolean, List<Person>> children = Stream.generate(new PersonSupplier()).limit(100)
                .collect(Collectors.partitioningBy(p -> p.getAge() < 18));
        System.out.println("Children number: " + children.get(true).size());
        System.out.println("Adult number: " + children.get(false).size());
    }

    public static void testStream() {
        Stream<String> stream = Stream.of("I", "love", "you", "too");
        List<String> list = stream.collect(Collectors.toList()); // (1)
// Set<String> set = stream.collect(Collectors.toSet()); // (2)
 Map<String, Integer> map = stream.collect(Collectors.toMap(Function.identity(), String::length));
    }
}



























